using System;
using System.Collections.Generic;
using System.IO;
using System.Text;

using Mono.Cecil.Cil;
using Mono.Cecil.PE;
using Mono.Cecil.Metadata;

using RVA = System.UInt32;

namespace Mono.Cecil {

	abstract class ModuleReader {

		protected Image image;
		protected ModuleDefinition module;

		protected ModuleReader (Image image, ReadingMode mode)
		{
			this.image = image;
			this.module = new ModuleDefinition (image);
			this.module.ReadingMode = mode;
		}

		protected abstract void ReadModule ();

		protected void ReadModuleManifest (MetadataReader reader)
		{
			reader.Populate (module);

			ReadAssembly (reader);
		}

		void ReadAssembly (MetadataReader reader)
		{
			var name = reader.ReadAssemblyNameDefinition ();
			if (name == null) {
				module.Kind = ModuleKind.NetModule;
				return;
			}

			var assembly = new AssemblyDefinition ();
			assembly.Name = name;

			module.Assembly = assembly;
			assembly.MainModule = module;
		}

		public static AssemblyDefinition CreateAssemblyFrom (Image image, ReadingMode mode)
		{
			var module = CreateModuleFrom (image, mode);
			return module.Assembly;
		}

		public static ModuleDefinition CreateModuleFrom (Image image, ReadingMode mode)
		{
			ModuleReader reader;

			switch (mode) {
			case ReadingMode.Immediate:
				reader = new ImmediateModuleReader (image);
				break;
			case ReadingMode.Deferred:
				reader = new DeferredModuleReader (image);
				break;
			default:
				throw new ArgumentException ();
			}

			reader.ReadModule ();
			return reader.module;
		}
	}

	sealed class ImmediateModuleReader : ModuleReader {

		public ImmediateModuleReader (Image image)
			: base (image, ReadingMode.Immediate)
		{
		}

		protected sealed override void ReadModule ()
		{
			this.module = this.module.Read (this.module, (module, reader) => {
				ReadModuleManifest (reader);

				if (module.HasAssemblyReferences)
					Read (module.AssemblyReferences);
				if (module.HasResources)
					Read (module.Resources);
				if (module.HasModuleReferences)
					Read (module.ModuleReferences);
				if (module.HasTypes)
					ReadModule (module);

				return module;
			});
		}

		public static void ReadModule (ModuleDefinition module)
		{
			var types = module.Types;

			for (int i = 0; i < types.Count; i++)
				ReadType (types [i]);
		}

		static void ReadType (TypeDefinition type)
		{
			ReadGenericParameters (type);

			if (type.HasInterfaces)
				Read (type.Interfaces);

			if (type.HasNestedTypes) {
				var nested_types = type.NestedTypes;
				for (int i = 0; i < nested_types.Count; i++)
					ReadType (nested_types [i]);
			}

			if (type.HasLayoutInfo)
				Read (type.ClassSize);

			if (type.HasFields)
				ReadFields (type);

			if (type.HasMethods)
				ReadMethods (type);

			if (type.HasProperties)
				ReadProperties (type);

			if (type.HasEvents)
				ReadEvents (type);

			ReadCustomAttributes (type);
		}

		static void ReadGenericParameters (IGenericParameterProvider provider)
		{
			if (!provider.HasGenericParameters)
				return;

			var parameters = provider.GenericParameters;

			for (int i = 0; i < parameters.Count; i++) {
				var parameter = parameters [i];

				if (parameter.HasConstraints)
					Read (parameter.Constraints);

				if (parameter.HasCustomAttributes)
					Read (parameter.CustomAttributes);
			}
		}

		static void ReadCustomAttributes (ICustomAttributeProvider provider)
		{
			if (provider.HasCustomAttributes)
				Read (provider.CustomAttributes);
		}

		static void ReadFields (TypeDefinition type)
		{
			var fields = type.Fields;

			for (int i = 0; i < fields.Count; i++) {
				var field = fields [i];

				if (field.HasConstant)
					Read (field.Constant);

				if (field.HasLayoutInfo)
					Read (field.Offset);

				if (field.RVA > 0)
					Read (field.InitialValue);

				ReadCustomAttributes (field);
			}
		}

		static void ReadMethods (TypeDefinition type)
		{
			var methods = type.Methods;

			for (int i = 0; i < methods.Count; i++) {
				var method = methods [i];

				ReadGenericParameters (method);

				if (method.HasParameters)
					ReadParameters (method);

				if (method.HasOverrides)
					Read (method.Overrides);

				if (method.IsPInvokeImpl)
					Read (method.PInvokeInfo);

				ReadCustomAttributes (method);

				var return_type = method.MethodReturnType;
				if (return_type.HasConstant)
					Read (return_type.Constant);

				ReadCustomAttributes (return_type);
			}
		}

		static void ReadParameters (MethodDefinition method)
		{
			var parameters = method.Parameters;

			for (int i = 0; i < parameters.Count; i++) {
				var parameter = parameters [i];

				if (parameter.HasConstant)
					Read (parameter.Constant);

				ReadCustomAttributes (parameter);
			}
		}

		static void ReadProperties (TypeDefinition type)
		{
			var properties = type.Properties;

			for (int i = 0; i < properties.Count; i++) {
				var property = properties [i];

				Read (property.GetMethod);
				Read (property.SetMethod);

				if (property.HasConstant)
					Read (property.Constant);

				ReadCustomAttributes (property);
			}
		}

		static void ReadEvents (TypeDefinition type)
		{
			var events = type.Events;

			for (int i = 0; i < events.Count; i++) {
				var @event = events [i];

				Read (@event.AddMethod);
				Read (@event.InvokeMethod);
				Read (@event.RemoveMethod);

				ReadCustomAttributes (@event);
			}
		}

		static void Read (object collection)
		{
		}
	}

	sealed class DeferredModuleReader : ModuleReader {

		public DeferredModuleReader (Image image)
			: base (image, ReadingMode.Deferred)
		{
		}

		protected sealed override void ReadModule ()
		{
			this.module.Read (this.module, (module, reader) => {
				ReadModuleManifest (reader);
				return module;
			});
		}
	}

	sealed class MetadataReader : ByteBuffer {

		internal Image image;
		internal ModuleDefinition module;
		internal MetadataSystem metadata;
		internal IGenericContext context;

		internal CodeReader code;

		public TypeSystem TypeSystem {
			get { return module.TypeSystem; }
		}

		public CodeReader Code {
			get {
				if (code == null)
					code = CodeReader.CreateCodeReader (this);

				return code;
			}
		}

		uint Position {
			get { return (uint) base.position; }
			set { base.position = (int) value; }
		}

		public MetadataReader (ModuleDefinition module)
			: base (module.Image.MetadataSection.Data)
		{
			this.image = module.Image;
			this.module = module;
			this.metadata = module.MetadataSystem;
		}

		int GetCodedIndexSize (CodedIndex index)
		{
			return image.GetCodedIndexSize (index);
		}

		uint ReadByIndexSize (int size)
		{
			switch (size) {
			case 2:
				return ReadUInt16 ();
			case 4:
				return ReadUInt32 ();
			default:
				throw new ArgumentException ();
			}
		}

		byte [] ReadBlob ()
		{
			return image.BlobHeap.Read (ReadBlobIndex ());
		}

		byte [] ReadBlob (uint signature)
		{
			return image.BlobHeap.Read (signature);
		}

		uint ReadBlobIndex ()
		{
			return ReadByIndexSize (image.BlobHeap.IndexSize);
		}

		string ReadString ()
		{
			return image.StringHeap [ReadByIndexSize (image.StringHeap.IndexSize)];
		}

		uint ReadStringIndex ()
		{
			return ReadByIndexSize (image.StringHeap.IndexSize);
		}

		int GetTableIndexSize (Table table)
		{
			return image.GetTableIndexSize (table);
		}

		int GetTableLength (Table table)
		{
			return image.GetTableLength (table);
		}

		uint ReadTableIndex (Table table)
		{
			return ReadByIndexSize (image.GetTableIndexSize (table));
		}

		MetadataToken ReadMetadataToken (CodedIndex index)
		{
			return index.GetMetadataToken (ReadByIndexSize (GetCodedIndexSize (index)));
		}

		int MoveTo (Table table)
		{
			var info = image.TableHeap [table];
			if (info.Length != 0)
				Position = info.Offset;

			return (int) info.Length;
		}

		bool MoveTo (Table table, uint row)
		{
			var info = image.TableHeap [table];
			var length = info.Length;
			if (length == 0 || row > length)
				return false;

			Position = info.Offset + (info.RowSize * (row - 1));
			return true;
		}

		public AssemblyNameDefinition ReadAssemblyNameDefinition ()
		{
			if (MoveTo (Table.Assembly) == 0)
				return null;

			var name = new AssemblyNameDefinition ();

			name.HashAlgorithm = (AssemblyHashAlgorithm) ReadUInt32 ();

			PopulateVersionAndFlags (name);

			name.PublicKey = ReadBlob ();

			PopulateNameAndCulture (name);

			return name;
		}

		public ModuleDefinition Populate (ModuleDefinition module)
		{
			MoveTo (Table.Module);

			Advance (2); // Generation

			module.Name = ReadString ();
			module.Mvid = image.GuidHeap [ReadByIndexSize (image.GuidHeap.IndexSize)];

			return module;
		}

		public AssemblyNameReferenceCollection ReadAssemblyReferences ()
		{
			var references = new AssemblyNameReferenceCollection ();

			int length = MoveTo (Table.AssemblyRef);
			for (uint i = 1; i <= length; i++) {
				var reference = new AssemblyNameReference ();
				reference.AttachToken (TokenType.AssemblyRef, i);

				PopulateVersionAndFlags (reference);

				reference.PublicKeyToken = ReadBlob ();

				PopulateNameAndCulture (reference);

				reference.Hash = ReadBlob ();

				references.Add (reference);
			}

			return references;
		}

		public MethodDefinition ReadEntryPoint ()
		{
			if (module.Kind != ModuleKind.Console
				&& module.Kind != ModuleKind.Windows)
				return null;

			var token = new MetadataToken (module.Image.EntryPointToken);

			return GetMethodDefinition (token.RID);
		}

		public ModuleDefinitionCollection ReadModules ()
		{
			var modules = new ModuleDefinitionCollection ();
			modules.Add (this.module);

			int length = MoveTo (Table.File);
			for (uint i = 1; i <= length; i++) {
				var attributes = (FileAttributes) ReadUInt32 ();
				var name = ReadString ();
				ReadBlobIndex ();

				if (attributes != FileAttributes.ContainsMetaData)
					continue;

				modules.Add (ModuleDefinition.ReadModule (
					GetModuleFileName (name),
					module.ReadingMode));
			}

			return modules;
		}

		string GetModuleFileName (string name)
		{
			if (module.FullyQualifiedName == null)
				throw new NotSupportedException ();

			var path = Path.GetDirectoryName (module.FullyQualifiedName);
			return Path.Combine (path, name);
		}

		public ModuleReferenceCollection ReadModuleReferences ()
		{
			var references = new ModuleReferenceCollection ();

			int length = MoveTo (Table.ModuleRef);
			for (uint i = 1; i <= length; i++) {
				var reference = new ModuleReference (ReadString ());
				reference.AttachToken (TokenType.ModuleRef, i);

				references.Add (reference);
			}

			return references;
		}

		public ResourceCollection ReadResources ()
		{
			var resources = new ResourceCollection ();

			int length = MoveTo (Table.ManifestResource);
			for (int i = 1; i <= length; i++) {
				var offset = ReadUInt32 ();
				var flags = (ManifestResourceAttributes) ReadUInt32 ();
				var name = ReadString ();
				var implementation = ReadMetadataToken (CodedIndex.Implementation);

				Resource resource;

				if (implementation.RID == 0) {
					resource = new EmbeddedResource (name, flags, offset, this);
				} else if (implementation.TokenType == TokenType.AssemblyRef) {
					resource = new AssemblyLinkedResource (name, flags) {
						Assembly = (AssemblyNameReference) GetTypeReferenceScope (implementation),
					};
				} else if (implementation.TokenType == TokenType.File) {
					var file_record = ReadFileRecord (implementation.RID);

					resource = new LinkedResource (name, flags) {
						File = file_record.Col2,
						Hash = ReadBlob (file_record.Col3)
					};
				} else
					throw new NotSupportedException ();

				resources.Add (resource);
			}

			return resources;
		}

		Row<FileAttributes, string, uint> ReadFileRecord (uint rid)
		{
			var position = this.position;

			if (!MoveTo (Table.File, rid))
				throw new ArgumentException ();

			var record = new Row<FileAttributes, string, uint> (
				(FileAttributes) ReadUInt32 (),
				ReadString (),
				ReadBlobIndex ());

			this.position = position;

			return record;
		}

		public MemoryStream GetManagedResourceStream (uint offset)
		{
			var rva = image.Resources.VirtualAddress;
			var section = image.GetSectionAtVirtualAddress (rva);
			var position = (rva - section.VirtualAddress) + offset;
			var buffer = section.Data;

			var length = buffer [position]
				| (buffer [position + 1] << 8)
				| (buffer [position + 2] << 16)
				| (buffer [position + 3] << 24);

			return new MemoryStream (buffer, (int) position + 4, length);
		}

		void PopulateVersionAndFlags (AssemblyNameReference name)
		{
			name.Version = new Version (
				ReadUInt16 (),
				ReadUInt16 (),
				ReadUInt16 (),
				ReadUInt16 ());

			name.Attributes = (AssemblyAttributes) ReadUInt32 ();
		}

		void PopulateNameAndCulture (AssemblyNameReference name)
		{
			name.Name = ReadString ();
			name.Culture = ReadString ();
		}

		public TypeDefinitionCollection ReadTypes ()
		{
			InitializeTypeDefinitions ();
			var types = new TypeDefinitionCollection (module);
			var mtypes = metadata.Types;

			for (int i = 0; i < mtypes.Length; i++) {
				var type = mtypes [i];
				if (IsNested (type.Attributes))
					continue;

				types.Add (type);
			}

			return types;
		}

		void InitializeTypeDefinitions ()
		{
			if (metadata.Types != null)
				return;

			InitializeFields ();
			InitializeMethods ();

			int length = MoveTo (Table.TypeDef);
			var types = metadata.Types = new TypeDefinition [length];

			for (uint i = 0; i < length; i++) {
				if (types [i] != null)
					continue;

				types [i] = ReadType (i + 1);
			}
		}

		static bool IsNested (TypeAttributes attributes)
		{
			switch (attributes & TypeAttributes.VisibilityMask) {
			case TypeAttributes.NestedAssembly:
			case TypeAttributes.NestedFamANDAssem:
			case TypeAttributes.NestedFamily:
			case TypeAttributes.NestedFamORAssem:
			case TypeAttributes.NestedPrivate:
			case TypeAttributes.NestedPublic:
				return true;
			default:
				return false;
			}
		}

		public bool HasNestedTypes (TypeDefinition type)
		{
			uint [] mapping;
			InitializeNestedTypes ();

			if (!metadata.TryGetNestedTypeMapping (type, out mapping))
				return false;

			return mapping.Length > 0;
		}

		public NestedTypeCollection ReadNestedTypes (TypeDefinition type)
		{
			InitializeNestedTypes ();
			var nested_types = new NestedTypeCollection (type);
			uint [] mapping;
			if (!metadata.TryGetNestedTypeMapping (type, out mapping))
				return nested_types;

			for (int i = 0; i < mapping.Length; i++)
				nested_types.Add (GetTypeDefinition (mapping [i]));

			metadata.RemoveNestedTypeMapping (type);

			return nested_types;
		}

		void InitializeNestedTypes ()
		{
			if (metadata.NestedTypes != null)
				return;

			metadata.NestedTypes = new Dictionary<uint, uint []> ();
			var length = MoveTo (Table.NestedClass);
			if (length == 0)
				return;

			for (int i = 1; i <= length; i++) {
				var nested = ReadTableIndex (Table.TypeDef);
				var declaring = ReadTableIndex (Table.TypeDef);

				AddNestedMapping (declaring, nested);
			}
		}

		void AddNestedMapping (uint declaring, uint nested)
		{
			metadata.SetNestedTypeMapping (declaring, AddMapping (metadata.NestedTypes, declaring, nested));
		}

		static TValue [] AddMapping<TKey, TValue> (Dictionary<TKey, TValue []> cache, TKey key, TValue value)
		{
			TValue [] mapped;
			if (!cache.TryGetValue (key, out mapped)) {
				mapped = new TValue [] { value };
				return mapped;
			}

			var new_mapped = new TValue [mapped.Length + 1];
			Array.Copy (mapped, new_mapped, mapped.Length);
			new_mapped [mapped.Length] = value;
			return new_mapped;
		}

		TypeDefinition ReadType (uint rid)
		{
			if (!MoveTo (Table.TypeDef, rid))
				return null;

			var attributes = (TypeAttributes) ReadUInt32 ();
			var type = new TypeDefinition (ReadString (), ReadString (), attributes);
			type.AttachToken (TokenType.TypeDef, rid);
			type.Scope = module;
			type.Module = module;

			metadata.AddTypeDefinition (type);

			this.context = type;

			type.BaseType = GetTypeDefOrRef (ReadMetadataToken (CodedIndex.TypeDefOrRef));

			type.fields_range = ReadFieldsRange (rid);
			type.methods_range = ReadMethodsRange (rid);

			return type;
		}

		Range ReadFieldsRange (uint type_index)
		{
			return ReadListRange (type_index, Table.TypeDef, Table.Field);
		}

		Range ReadMethodsRange (uint type_index)
		{
			return ReadListRange (type_index, Table.TypeDef, Table.Method);
		}

		Range ReadListRange (uint current_index, Table current, Table target)
		{
			var list = new Range ();

			list.Start = ReadTableIndex (target);

			uint next_index;
			var current_table = image.TableHeap [current];

			if (current_index == current_table.Length)
				next_index = image.TableHeap [target].Length + 1;
			else {
				var position = Position;
				Position += (uint) (current_table.RowSize - GetTableIndexSize (target));
				next_index = ReadTableIndex (target);
				Position = position;
			}

			list.Length = next_index - list.Start;

			return list;
		}

		public TypeDefinition ReadTypeLayout (TypeDefinition type)
		{
			InitializeTypeLayouts ();
			Row<ushort, uint> class_layout;
			var rid = type.MetadataToken.RID;
			if (!metadata.ClassLayouts.TryGetValue (rid, out class_layout))
				return type;

			type.PackingSize = (short) class_layout.Col1;
			type.ClassSize = (int) class_layout.Col2;

			metadata.ClassLayouts.Remove (rid);

			return type;
		}

		void InitializeTypeLayouts ()
		{
			if (metadata.ClassLayouts != null)
				return;

			var class_layouts = metadata.ClassLayouts = new Dictionary<uint, Row<ushort, uint>> ();

			int length = MoveTo (Table.ClassLayout);
			for (uint i = 0; i < length; i++) {
				var packing_size = ReadUInt16 ();
				var class_size = ReadUInt32 ();

				var parent = ReadTableIndex (Table.TypeDef);

				class_layouts.Add (parent, new Row<ushort, uint> (packing_size, class_size));
			}
		}

		public TypeReference GetTypeDefOrRef (MetadataToken token)
		{
			if (token.RID == 0)
				return null;

			TypeReference type = null;
			var position = this.Position;
			var context = this.context;

			switch (token.TokenType) {
			case TokenType.TypeDef:
				type = GetTypeDefinition (token.RID);
				break;
			case TokenType.TypeRef:
				type = GetTypeReference (token.RID);
				break;
			case TokenType.TypeSpec:
				type = GetTypeSpecification (token.RID);
				break;
			default:
				throw new NotSupportedException ();
			}

			this.Position = position;
			this.context = context;
			return type;
		}

		TypeDefinition GetTypeDefinition (uint rid)
		{
			InitializeTypeDefinitions ();

			var type = metadata.GetTypeDefinition (rid);
			if (type != null)
				return type;

			return ReadTypeDefinition (rid);
		}

		TypeDefinition ReadTypeDefinition (uint rid)
		{
			if (!MoveTo (Table.TypeDef, rid))
				return null;

			return ReadType (rid);
		}

		void InitializeTypeReferences ()
		{
			if (metadata.TypeReferences != null)
				return;

			metadata.TypeReferences = new TypeReference [GetTableLength (Table.TypeRef)];
		}

		TypeReference GetTypeReference (uint rid)
		{
			InitializeTypeReferences ();

			var type = metadata.GetTypeReference (rid);
			if (type != null)
				return type;

			type = ReadTypeReference (rid);
			metadata.AddTypeReference (type);
			return type;
		}

		TypeReference ReadTypeReference (uint rid)
		{
			if (!MoveTo (Table.TypeRef, rid))
				return null;

			TypeReference declaring_type = null;
			var scope = ReadMetadataToken (CodedIndex.ResolutionScope);

			if (scope.TokenType == TokenType.TypeRef)
				declaring_type = GetTypeDefOrRef (scope);

			var type = new TypeReference (
				ReadString (),
				ReadString (),
				declaring_type != null
					? declaring_type.Scope
					: GetTypeReferenceScope (scope));

			type.DeclaringType = declaring_type;
			type.AttachToken (TokenType.TypeRef, rid);
			type.Module = module;

			return type;
		}

		IMetadataScope GetTypeReferenceScope (MetadataToken scope)
		{
			switch (scope.TokenType) {
			// FIXME: both assembly refs and module refs should be in their
			// own arrays, in case of someone modify the collections before
			// this code is called
			case TokenType.AssemblyRef:
				return module.AssemblyReferences [(int) scope.RID - 1];
			case TokenType.ModuleRef:
				return module.ModuleReferences [(int) scope.RID - 1];
			case TokenType.Module:
				return module;
			default:
				throw new NotSupportedException ();
			}
		}

		TypeReference GetTypeSpecification (uint rid)
		{
			if (!MoveTo (Table.TypeSpec, rid))
				return null;

			using (var reader = ReadSignature (ReadBlobIndex ()))
				return reader.ReadTypeSignature ();
		}

		SigReader ReadSignature (uint signature)
		{
			return new SigReader (signature, this);
		}

		public bool HasInterfaces (TypeDefinition type)
		{
			InitializeInterfaces ();
			MetadataToken [] mapping;

			return metadata.TryGetInterfaceMapping (type, out mapping);
		}

		public InterfaceCollection ReadInterfaces (TypeDefinition type)
		{
			InitializeInterfaces ();
			MetadataToken [] mapping;
			var interfaces = new InterfaceCollection ();

			if (!metadata.TryGetInterfaceMapping (type, out mapping))
				return interfaces;

			this.context = type;

			for (int i = 0; i < mapping.Length; i++)
				interfaces.Add (GetTypeDefOrRef (mapping [i]));

			metadata.RemoveInterfaceMapping (type);

			return interfaces;
		}

		void InitializeInterfaces ()
		{
			if (metadata.Interfaces != null)
				return;

			metadata.Interfaces = new Dictionary<uint, MetadataToken []> ();

			int length = MoveTo (Table.InterfaceImpl);
			for (int i = 0; i < length; i++) {
				var type = ReadTableIndex (Table.TypeDef);
				var @interface = ReadMetadataToken (CodedIndex.TypeDefOrRef);

				AddInterfaceMapping (type, @interface);
			}
		}

		void AddInterfaceMapping (uint type, MetadataToken @interface)
		{
			metadata.SetInterfaceMapping (type, AddMapping (metadata.Interfaces, type, @interface));
		}

		public FieldDefinitionCollection ReadFields (TypeDefinition type)
		{
			var fields = new FieldDefinitionCollection (type);
			var fields_range = type.fields_range;
			this.context = type;

			MoveTo (Table.Field, fields_range.Start);
			for (uint i = 0; i < fields_range.Length; i++) {
				var attributes = (FieldAttributes) ReadUInt16 ();
				var name = ReadString ();
				var signature = ReadBlobIndex ();

				var field = new FieldDefinition (name, ReadFieldType (signature), attributes);
				var field_rid = fields_range.Start + i;
				field.AttachToken (TokenType.Field, field_rid);
				metadata.AddFieldDefinition (field);
				fields.Add (field);
			}

			return fields;
		}

		void InitializeFields ()
		{
			if (metadata.Fields != null)
				return;

			metadata.Fields = new FieldDefinition [GetTableLength (Table.Field)];
		}

		TypeReference ReadFieldType (uint signature)
		{
			using (var reader = ReadSignature (signature)) {
				const byte field_sig = 0x6;

				if (reader.ReadByte () != field_sig)
					throw new NotSupportedException ();

				return reader.ReadTypeSignature ();
			}
		}

		public int ReadFieldRVA (FieldDefinition field)
		{
			InitializeFieldRVAs ();
			var rid = field.MetadataToken.RID;

			RVA rva;
			if (!metadata.FieldRVAs.TryGetValue (rid, out rva))
				return 0;

			var size = GetFieldTypeSize (field.FieldType);

			if (size == 0 || rva == 0)
				return 0;

			metadata.FieldRVAs.Remove (rid);

			field.RVA = (int) rva;
			field.InitialValue = GetFieldInitializeValue (size, rva);

			return (int) rva;
		}

		byte [] GetFieldInitializeValue (int size, RVA rva)
		{
			var section = image.GetSectionAtVirtualAddress (rva);
			if (section == null)
				return Empty<byte>.Array;

			var value = new byte [size];
			Buffer.BlockCopy (section.Data, (int) (rva - section.VirtualAddress), value, 0, size);
			return value;
		}

		int GetFieldTypeSize (TypeReference type)
		{
			int size = 0;

			switch (type.etype) {
			case ElementType.Boolean:
			case ElementType.U1:
			case ElementType.I1:
				size = 1;
				break;
			case ElementType.U2:
			case ElementType.I2:
			case ElementType.Char:
				size = 2;
				break;
			case ElementType.U4:
			case ElementType.I4:
			case ElementType.R4:
				size = 4;
				break;
			case ElementType.U8:
			case ElementType.I8:
			case ElementType.R8:
				size = 8;
				break;
			case ElementType.Ptr:
			case ElementType.FnPtr:
				size = IntPtr.Size;
				break;
			case ElementType.CModOpt:
				return GetFieldTypeSize (((OptionalModifierType) type).ElementType);
			case ElementType.CModReqD:
				return GetFieldTypeSize (((RequiredModifierType) type).ElementType);
			default:
				var type_def = type.Resolve ();

				if (type_def != null && type_def.HasLayoutInfo)
					size = type_def.ClassSize;

				break;
			}

			return size;
		}

		void InitializeFieldRVAs ()
		{
			if (metadata.FieldRVAs != null)
				return;

			var field_rvas = metadata.FieldRVAs = new Dictionary<uint, uint> ();

			int length = MoveTo (Table.FieldRVA);
			for (int i = 0; i < length; i++) {
				var rva = ReadUInt32 ();
				var field = ReadTableIndex (Table.Field);

				field_rvas.Add (field, rva);
			}
		}

		public int? ReadFieldLayout (FieldDefinition field)
		{
			InitializeFieldLayouts ();
			var rid = field.MetadataToken.RID;
			uint offset;
			if (!metadata.FieldLayouts.TryGetValue (rid, out offset))
				return null;

			metadata.FieldLayouts.Remove (rid);

			field.Offset = (int) offset;
			return (int?) offset;
		}

		void InitializeFieldLayouts ()
		{
			if (metadata.FieldLayouts != null)
				return;

			var field_layouts = metadata.FieldLayouts = new Dictionary<uint, uint> ();

			int length = MoveTo (Table.FieldLayout);
			for (int i = 0; i < length; i++) {
				var offset = ReadUInt32 ();
				var field = ReadTableIndex (Table.Field);

				field_layouts.Add (field, offset);
			}
		}

		public bool HasEvents (TypeDefinition type)
		{
			InitializeEvents ();

			Range range;
			if (!metadata.TryGetEventsRange (type, out range))
				return false;

			return range.Length > 0;
		}

		public EventDefinitionCollection ReadEvents (TypeDefinition type)
		{
			InitializeEvents ();
			Range range;
			var events = new EventDefinitionCollection (type);

			if (!metadata.TryGetEventsRange (type, out range))
				return events;

			metadata.RemoveEventsRange (type);

			if (range.Length == 0 || !MoveTo (Table.Event, range.Start))
				return events;

			this.context = type;

			for (uint i = 0; i < range.Length; i++) {
				var attributes = (EventAttributes) ReadUInt16 ();
				var name = ReadString ();
				var event_type = GetTypeDefOrRef (ReadMetadataToken (CodedIndex.TypeDefOrRef));

				var @event = new EventDefinition (name, event_type, attributes);
				@event.AttachToken (TokenType.Event, range.Start + i);
				events.Add (@event);
			}

			return events;
		}

		void InitializeEvents ()
		{
			if (metadata.Events != null)
				return;

			metadata.Events = new Dictionary<uint, Range> ();

			int length = MoveTo (Table.EventMap);

			for (uint i = 1; i <= length; i++) {
				var type_rid = ReadTableIndex (Table.TypeDef);
				Range events_range = ReadEventsRange (i);
				metadata.AddEventsRange (type_rid, events_range);
			}
		}

		Range ReadEventsRange (uint rid)
		{
			return ReadListRange (rid, Table.EventMap, Table.Event);
		}

		public bool HasProperties (TypeDefinition type)
		{
			InitializeProperties ();

			Range range;
			if (!metadata.TryGetPropertiesRange (type, out range))
				return false;

			return range.Length > 0;
		}

		public PropertyDefinitionCollection ReadProperties (TypeDefinition type)
		{
			InitializeProperties ();

			Range range;
			var properties = new PropertyDefinitionCollection (type);

			if (!metadata.TryGetPropertiesRange (type, out range))
				return properties;

			metadata.RemovePropertiesRange (type);

			if (range.Length == 0 || !MoveTo (Table.Property, range.Start))
				return properties;

			this.context = type;

			for (uint i = 0; i < range.Length; i++) {
				var attributes = (PropertyAttributes) ReadUInt16 ();
				var name = ReadString ();
				var signature = ReadBlobIndex ();
				using (var reader = ReadSignature (signature)) {
					const byte property_signature = 0x8;

					var calling_convention = reader.ReadByte ();

					if ((calling_convention & property_signature) == 0)
						throw new NotSupportedException ();

					var has_this = (calling_convention & 0x20) != 0;

					reader.ReadCompressedUInt32 (); // count

					var property = new PropertyDefinition (name, reader.ReadTypeSignature (), attributes);
					property.HasThis = has_this;
					property.AttachToken (TokenType.Property, range.Start + i);
					properties.Add (property);
				}
			}

			return properties;
		}

		void InitializeProperties ()
		{
			if (metadata.Properties != null)
				return;

			metadata.Properties = new Dictionary<uint, Range> ();

			int length = MoveTo (Table.PropertyMap);

			for (uint i = 1; i <= length; i++) {
				var type_rid = ReadTableIndex (Table.TypeDef);
				Range properties_range = ReadPropertiesRange (i);
				metadata.AddPropertiesRange (type_rid, properties_range);
			}
		}

		Range ReadPropertiesRange (uint rid)
		{
			return ReadListRange (rid, Table.PropertyMap, Table.Property);
		}

		public MethodSemanticsAttributes ReadMethodSemantics (MethodDefinition method)
		{
			InitializeMethodSemantics ();
			Row<MethodSemanticsAttributes, MetadataToken> row;
			if (!metadata.Semantics.TryGetValue (method.MetadataToken.RID, out row))
				return MethodSemanticsAttributes.None;

			method.SemanticsAttributes = row.Col1;
			var type = method.DeclaringType;

			switch (row.Col1) {
			case MethodSemanticsAttributes.AddOn:
				GetEvent (type, row.Col2).AddMethod = method;
				break;
			case MethodSemanticsAttributes.Fire:
				GetEvent (type, row.Col2).InvokeMethod = method;
				break;
			case MethodSemanticsAttributes.RemoveOn:
				GetEvent (type, row.Col2).RemoveMethod = method;
				break;
			case MethodSemanticsAttributes.Getter:
				GetProperty (type, row.Col2).GetMethod = method;
				break;
			case MethodSemanticsAttributes.Setter:
				GetProperty (type, row.Col2).SetMethod = method;
				break;
			default:
				throw new NotSupportedException ();
			}

			return row.Col1;
		}

		EventDefinition GetEvent (TypeDefinition type, MetadataToken token)
		{
			if (token.TokenType != TokenType.Event)
				throw new ArgumentException ();

			return GetMember (type.Events, token);
		}

		PropertyDefinition GetProperty (TypeDefinition type, MetadataToken token)
		{
			if (token.TokenType != TokenType.Property)
				throw new ArgumentException ();

			return GetMember (type.Properties, token);
		}

		TMember GetMember<TMember> (IList<TMember> members, MetadataToken token) where TMember : IMemberDefinition
		{
			for (int i = 0; i < members.Count; i++) {
				var member = members [i];
				if (member.MetadataToken == token)
					return member;
			}

			throw new ArgumentException ();
		}

		void InitializeMethodSemantics ()
		{
			if (metadata.Semantics != null)
				return;

			var semantics = metadata.Semantics = new Dictionary<uint, Row<MethodSemanticsAttributes, MetadataToken>> ();

			int length = MoveTo (Table.MethodSemantics);
			for (uint i = 0; i < length; i++) {
				var attributes = (MethodSemanticsAttributes) ReadUInt16 ();
				var method_rid = ReadTableIndex (Table.Method);
				var association = ReadMetadataToken (CodedIndex.HasSemantics);

				semantics.Add (
					method_rid,
					new Row<MethodSemanticsAttributes, MetadataToken> (attributes, association));
			}
		}

		public PropertyDefinition ReadMethods (PropertyDefinition property)
		{
			ReadAllSemantics (property.DeclaringType);
			return property;
		}

		public EventDefinition ReadMethods (EventDefinition @event)
		{
			ReadAllSemantics (@event.DeclaringType);
			return @event;
		}

		void ReadAllSemantics (TypeDefinition type)
		{
			var methods = type.Methods;
			for (int i = 0; i < methods.Count; i++)
				methods [i].ReadSemantics ();
		}

		Range ReadParametersRange (uint method_rid)
		{
			return ReadListRange (method_rid, Table.Method, Table.Param);
		}

		public MethodDefinitionCollection ReadMethods (TypeDefinition type)
		{
			var methods = new MethodDefinitionCollection (type);
			var methods_range = type.methods_range;
			MoveTo (Table.Method, methods_range.Start);
			for (uint i = 0; i < methods_range.Length; i++)
				ReadMethod (methods_range.Start + i, methods);

			return methods;
		}

		void InitializeMethods ()
		{
			if (metadata.Methods != null)
				return;

			metadata.Methods = new MethodDefinition [GetTableLength (Table.Method)];
		}

		MethodDefinition ReadMethod (uint method_rid, MethodDefinitionCollection methods)
		{
			var method = new MethodDefinition ();
			method.RVA = (int) ReadUInt32 ();
			method.ImplAttributes = (MethodImplAttributes) ReadUInt16 ();
			method.Attributes = (MethodAttributes) ReadUInt16 ();
			method.Name = ReadString ();
			method.AttachToken (TokenType.Method, method_rid);

			methods.Add (method); // attach method

			var signature = ReadBlobIndex ();
			var param_range = ReadParametersRange (method_rid);

			this.context = method;

			ReadMethodSignature (signature, method);
			metadata.AddMethodDefinition (method);

			if (param_range.Length == 0)
				return method;

			var position = base.position;
			ReadParameters (method, param_range);
			base.position = position;

			return method;
		}

		void ReadParameters (MethodDefinition method, Range param_range)
		{
			MoveTo (Table.Param, param_range.Start);
			for (uint i = 0; i < param_range.Length; i++) {
				var attributes = (ParameterAttributes) ReadUInt16 ();
				var sequence = ReadUInt16 ();
				var name = ReadString ();

				var parameter = sequence == 0
					? method.MethodReturnType.Parameter
					: method.Parameters [sequence - 1];

				parameter.AttachToken (TokenType.Param, param_range.Start + i);
				parameter.Name = name;
				parameter.Attributes = attributes;
			}
		}

		void ReadMethodSignature (uint signature, IMethodSignature method)
		{
			using (var reader = ReadSignature (signature)) {
				reader.ReadMethodSignature (method);
			}
		}

		public PInvokeInfo ReadPInvokeInfo (MethodDefinition method)
		{
			InitializePInvokes ();
			Row<PInvokeAttributes, uint, uint> row;

			var rid = method.MetadataToken.RID;

			if (!metadata.PInvokes.TryGetValue (rid, out row))
				return null;

			metadata.PInvokes.Remove (rid);

			return new PInvokeInfo (
				row.Col1,
				image.StringHeap [row.Col2],
				module.ModuleReferences [(int) row.Col3 - 1]);
		}

		void InitializePInvokes ()
		{
			if (metadata.PInvokes != null)
				return;

			var pinvokes = metadata.PInvokes = new Dictionary<uint, Row<PInvokeAttributes, uint, uint>> ();

			int length = MoveTo (Table.ImplMap);
			for (int i = 1; i <= length; i++) {
				var attributes = (PInvokeAttributes) ReadUInt16 ();
				var method = ReadMetadataToken (CodedIndex.MemberForwarded);
				var name = ReadStringIndex ();
				var scope = ReadTableIndex (Table.File);

				if (method.TokenType != TokenType.Method)
					throw new NotSupportedException ();

				pinvokes.Add (method.RID, new Row<PInvokeAttributes, uint, uint> (attributes, name, scope));
			}
		}

		public bool HasGenericParameters (IGenericParameterProvider provider)
		{
			InitializeGenericParameters ();

			Range range;
			if (!metadata.TryGetGenericParameterRange (provider, out range))
				return false;

			return range.Length > 0;
		}

		public GenericParameterCollection ReadGenericParameters (IGenericParameterProvider provider)
		{
			InitializeGenericParameters ();
			var generic_parameters = new GenericParameterCollection ();

			Range range;
			if (!metadata.TryGetGenericParameterRange (provider, out range))
				return generic_parameters;

			if (!MoveTo (Table.GenericParam, range.Start))
				return generic_parameters;

			metadata.RemoveGenericParameterRange (provider);

			for (uint i = 0; i < range.Length; i++) {
				ReadUInt16 (); // index
				var flags = (GenericParameterAttributes) ReadUInt16 ();
				ReadMetadataToken (CodedIndex.TypeOrMethodDef);
				var name = ReadString ();

				var parameter = new GenericParameter (name, provider);
				parameter.AttachToken (TokenType.GenericParam, range.Start + i);
				parameter.Attributes = flags;

				generic_parameters.Add (parameter);
			}

			return generic_parameters;
		}

		// TODO : really ugly
		void InitializeGenericParameters ()
		{
			if (metadata.GenericParameters != null)
				return;

			metadata.GenericParameters = new Dictionary<MetadataToken, Range> ();

			int length = MoveTo (Table.GenericParam);
			if (length == 0)
				return;

			MetadataToken owner = MetadataToken.Zero;
			Range range = new Range (1, 0);

			for (uint i = 1; i <= length; i++) {
				Advance (4);

				var next = ReadMetadataToken (CodedIndex.TypeOrMethodDef);
				ReadStringIndex ();

				if (i == 1) {
					owner = next;
					range.Length++;
				} else if (next != owner) {
					metadata.AddGenericParameterRange (owner, range);
					range = new Range (i, 1);
					owner = next;
				} else
					range.Length++;
			}

			if (owner != MetadataToken.Zero)
				metadata.AddGenericParameterRange (owner, range);
		}

		public bool HasGenericConstraints (GenericParameter generic_parameter)
		{
			InitializeGenericConstraints ();
			MetadataToken [] mapping;

			if (!metadata.TryGetGenericConstraintMapping (generic_parameter, out mapping))
				return false;

			return mapping.Length > 0;
		}

		public GenericConstraintCollection ReadGenericConstraints (GenericParameter generic_parameter)
		{
			InitializeGenericConstraints ();
			var constraints = new GenericConstraintCollection ();

			MetadataToken [] mapping;

			if (!metadata.TryGetGenericConstraintMapping (generic_parameter, out mapping))
				return constraints;

			this.context = (IGenericContext) generic_parameter.Owner;

			for (int i = 0; i < mapping.Length; i++)
				constraints.Add (GetTypeDefOrRef (mapping [i]));

			metadata.RemoveGenericConstraintMapping (generic_parameter);

			return constraints;
		}

		void InitializeGenericConstraints ()
		{
			if (metadata.GenericConstraints != null)
				return;

			metadata.GenericConstraints = new Dictionary<uint, MetadataToken []> ();

			var length = MoveTo (Table.GenericParamConstraint);

			for (int i = 1; i <= length; i++)
				AddGenericConstraintMapping (
					ReadTableIndex (Table.GenericParam),
					ReadMetadataToken (CodedIndex.TypeDefOrRef));
		}

		void AddGenericConstraintMapping (uint generic_parameter, MetadataToken constraint)
		{
			metadata.SetGenericConstraintMapping (
				generic_parameter,
				AddMapping (metadata.GenericConstraints, generic_parameter, constraint));
		}

		public bool HasOverrides (MethodDefinition method)
		{
			InitializeOverrides ();
			MetadataToken [] mapping;

			if (!metadata.TryGetOverrideMapping (method, out mapping))
				return false;

			return mapping.Length > 0;
		}

		public OverrideCollection ReadOverrides (MethodDefinition method)
		{
			InitializeOverrides ();
			MetadataToken [] mapping;

			var overrides = new OverrideCollection ();

			if (!metadata.TryGetOverrideMapping (method, out mapping))
				return overrides;

			this.context = method;

			for (int i = 0; i < mapping.Length; i++)
				overrides.Add ((MethodReference) LookupToken (mapping [i]));

			metadata.RemoveOverrideMapping (method);

			return overrides;
		}

		void InitializeOverrides ()
		{
			if (metadata.Overrides != null)
				return;

			metadata.Overrides = new Dictionary<uint, MetadataToken []> ();

			var length = MoveTo (Table.MethodImpl);

			for (int i = 1; i <= length; i++) {
				ReadTableIndex (Table.TypeDef);

				var method = ReadMetadataToken (CodedIndex.MethodDefOrRef);
				if (method.TokenType != TokenType.Method)
					throw new NotSupportedException ();

				var @override = ReadMetadataToken (CodedIndex.MethodDefOrRef);

				AddOverrideMapping (method.RID, @override);
			}
		}

		void AddOverrideMapping (uint method_rid, MetadataToken @override)
		{
			metadata.SetOverrideMapping (
				method_rid,
				AddMapping (metadata.Overrides, method_rid, @override));
		}

		public MethodBody ReadMethodBody (MethodDefinition method)
		{
			return Code.ReadMethodBody (method);
		}

		public CallSite ReadCallSite (MetadataToken token)
		{
			if (!MoveTo (Table.StandAloneSig, token.RID))
				return null;

			var signature = ReadBlobIndex ();

			var call_site = new CallSite ();

			ReadMethodSignature (signature, call_site);

			return call_site;
		}

		public void ReadVariables (MethodBody body)
		{
			if (!MoveTo (Table.StandAloneSig, body.LocalVarToken.RID))
				return;

			using (var reader = ReadSignature (ReadBlobIndex ())) {
				const byte local_sig = 0x7;

				if (reader.ReadByte () != local_sig)
					throw new NotSupportedException ();

				var count = reader.ReadCompressedUInt32 ();
				if (count == 0)
					return;

				var variables = body.Variables;

				for (int i = 0; i < count; i++)
					variables.Add (new VariableDefinition (reader.ReadTypeSignature ()));
			}
		}

		public IMetadataTokenProvider LookupToken (MetadataToken token)
		{
			IMetadataTokenProvider element;
			var position = this.position;

			switch (token.TokenType) {
			case TokenType.TypeDef:
				element = GetTypeDefinition (token.RID);
				break;
			case TokenType.TypeRef:
				element = GetTypeReference (token.RID);
				break;
			case TokenType.TypeSpec:
				element = GetTypeSpecification (token.RID);
				break;
			case TokenType.Field:
				element = GetFieldDefinition (token.RID);
				break;
			case TokenType.Method:
				element = GetMethodDefinition (token.RID);
				break;
			case TokenType.MemberRef:
				element = GetMemberReference (token.RID);
				break;
			case TokenType.MethodSpec:
				element = GetMethodSpecification (token.RID);
				break;
			default:
				throw new NotSupportedException ();
			}

			this.position = position;
			return element;
		}

		public FieldDefinition GetFieldDefinition (uint rid)
		{
			InitializeTypeDefinitions ();

			var field = metadata.GetFieldDefinition (rid);
			if (field != null)
				return field;

			return LookupField (rid);
		}

		FieldDefinition LookupField (uint rid)
		{
			var type = metadata.GetFieldDeclaringType (rid);
			if (type == null)
				throw new NotSupportedException ();

			InitializeCollection (type.Fields);

			return metadata.GetFieldDefinition (rid);
		}

		public MethodDefinition GetMethodDefinition (uint rid)
		{
			InitializeTypeDefinitions ();

			var method = metadata.GetMethodDefinition (rid);
			if (method != null)
				return method;

			return LookupMethod (rid);
		}

		MethodDefinition LookupMethod (uint rid)
		{
			var type = metadata.GetMethodDeclaringType (rid);
			if (type == null)
				throw new NotSupportedException ();

			InitializeCollection (type.Methods);

			return metadata.GetMethodDefinition (rid);
		}

		MethodSpecification GetMethodSpecification (uint rid)
		{
			if (!MoveTo (Table.MethodSpec, rid))
				return null;

			var method = (MethodReference) LookupToken (
				ReadMetadataToken (CodedIndex.MethodDefOrRef));
			var signature = ReadBlobIndex ();

			var instance = new GenericInstanceMethod (method);

			ReadMethodSpecSignature (signature, instance);

			return instance;
		}

		void ReadMethodSpecSignature (uint signature, GenericInstanceMethod instance)
		{
			using (var reader = ReadSignature (signature)) {
				const byte methodspec_sig = 0x0a;

				var call_conv = reader.ReadByte ();

				if (call_conv != methodspec_sig)
					throw new NotSupportedException ();

				reader.ReadGenericInstanceSignature (instance);
			}
		}

		MemberReference GetMemberReference (uint rid)
		{
			InitializeMemberReferences ();

			var member = metadata.GetMemberReference (rid);
			if (member != null)
				return member;

			member = ReadMemberReference (rid);
			metadata.AddMemberReference (member);
			return member;
		}

		MemberReference ReadMemberReference (uint rid)
		{
			if (!MoveTo (Table.MemberRef, rid))
				return null;

			var token = ReadMetadataToken (CodedIndex.MemberRefParent);
			var name = ReadString ();
			var signature = ReadBlobIndex ();

			TypeReference declaring_type = null;

			switch (token.TokenType) {
			case TokenType.TypeDef:
			case TokenType.TypeRef:
			case TokenType.TypeSpec:
				declaring_type = GetTypeDefOrRef (token);
				break;
			default:
				throw new NotSupportedException ();
			}

			this.context = declaring_type;

			var member = ReadMemberReferenceSignature (signature, declaring_type);
			member.Name = name;
			member.AttachToken (TokenType.MemberRef, rid);

			return member;
		}

		MemberReference ReadMemberReferenceSignature (uint signature, TypeReference declaring_type)
		{
			using (var reader = ReadSignature (signature)) {
				const byte field_sig = 0x6;

				if (reader.buffer [reader.position] == field_sig) {
					reader.position++;
					var field = new FieldReference ();
					field.DeclaringType = declaring_type;
					field.FieldType = reader.ReadTypeSignature ();
					return field;
				} else {
					var method = new MethodReference ();
					method.DeclaringType = declaring_type;
					reader.ReadMethodSignature (method);
					return method;
				}
			}
		}

		void InitializeMemberReferences ()
		{
			if (metadata.MemberReferences != null)
				return;

			metadata.MemberReferences = new MemberReference [GetTableLength (Table.MemberRef)];
		}

		void InitializeConstants ()
		{
			if (metadata.Constants != null)
				return;

			var constants = metadata.Constants = new Dictionary<MetadataToken, Row<ElementType, uint>> ();

			var length = MoveTo (Table.Constant);
			if (length == 0)
				return;

			for (uint i = 1; i <= length; i++) {
				var type = (ElementType) ReadUInt16 ();
				var owner = ReadMetadataToken (CodedIndex.HasConstant);
				var signature = ReadBlobIndex ();

				constants.Add (owner, new Row<ElementType, uint> (type, signature));
			}
		}

		public object ReadConstant (IConstantProvider owner)
		{
			InitializeConstants ();

			Row<ElementType, uint> row;
			if (!metadata.Constants.TryGetValue (owner.MetadataToken, out row))
				return Constants.NoValue;

			switch (row.Col1) {
			case ElementType.Class:
			case ElementType.Object:
				return null;
			case ElementType.String:
				return ReadConstantString (ReadBlob (row.Col2));
			default:
				return ReadConstantSignature (row.Col1, row.Col2);
			}
		}

		string ReadConstantString (byte [] blob)
		{
			var length = blob.Length;
			if ((length & 1) == 1)
				length--;

			return Encoding.Unicode.GetString (blob, 0, length);
		}

		object ReadConstantSignature (ElementType type, uint signature)
		{
			using (var reader = ReadSignature (signature)) {
				if (BitConverter.IsLittleEndian)
					return reader.ReadConstantSignatureLittleEndian (type);

				return reader.ReadConstantSignatureBigEndian (type, ReadBlob (signature));
			}
		}

		void InitializeCustomAttributes ()
		{
			if (metadata.CustomAttributes != null)
				return;

			metadata.CustomAttributes = new Dictionary<MetadataToken, Range> ();

			var length = MoveTo (Table.CustomAttribute);
			if (length == 0)
				return;

			MetadataToken owner = MetadataToken.Zero;
			Range range = new Range (1, 0);

			for (uint i = 1; i <= length; i++) {
				var next = ReadMetadataToken (CodedIndex.HasCustomAttribute);
				ReadMetadataToken (CodedIndex.CustomAttributeType);
				ReadBlobIndex ();

				if (i == 1) {
					owner = next;
					range.Length++;
				} else if (next != owner) {
					metadata.AddCustomAttributeRange (owner, range);
					range = new Range (i, 1);
					owner = next;
				} else
					range.Length++;
			}

			if (owner != MetadataToken.Zero)
				metadata.AddCustomAttributeRange (owner, range);
		}

		public bool HasCustomAttributes (ICustomAttributeProvider owner)
		{
			InitializeCustomAttributes ();

			Range range;
			if (!metadata.TryGetCustomAttributeRange (owner, out range))
				return false;

			return range.Length > 0;
		}

		public CustomAttributeCollection ReadCustomAttributes (ICustomAttributeProvider owner)
		{
			InitializeCustomAttributes ();

			Range range;
			var custom_attributes = new CustomAttributeCollection ();

			if (!metadata.TryGetCustomAttributeRange (owner, out range))
				return custom_attributes;

			if (!MoveTo (Table.CustomAttribute, range.Start))
				return custom_attributes;

			for (int i = 0; i < range.Length; i++) {
				ReadMetadataToken (CodedIndex.HasCustomAttribute);

				var constructor = (MethodReference) LookupToken (
					ReadMetadataToken (CodedIndex.CustomAttributeType));

				var signature = ReadBlobIndex ();

				var attribute = new CustomAttribute (signature, constructor);

				//ReadCustomAttributeSignature (signature, attribute);

				custom_attributes.Add (attribute);
			}

			metadata.RemoveCustomAttributeRange (owner);

			return custom_attributes;
		}

		public byte [] ReadCustomAttributeBlob (uint signature)
		{
			return ReadBlob (signature);
		}

		public void ReadCustomAttributeSignature (uint signature, CustomAttribute attribute)
		{
			using (var reader = ReadSignature (signature)) {
				if (reader.ReadUInt16 () != 0x0001)
				    throw new InvalidOperationException ();

				var constructor = attribute.Constructor;
				if (constructor.HasParameters)
					reader.ReadCustomAttributeConstructorArguments (attribute, constructor.Parameters);

				var named = reader.ReadUInt16 ();

				if (named > 0)
					reader.ReadCustomAttributeNamedArguments (attribute, named);
			}
		}

		void InitializeCollection (object o)
		{
		}
	}

	class SigReader : ByteBuffer, IDisposable {

		MetadataReader reader;
#if DEBUG
		uint start, sig_length;
#endif

		TypeSystem TypeSystem {
			get { return reader.TypeSystem; }
		}

		public SigReader (uint blob, MetadataReader reader)
			: base (reader.buffer)
		{
			this.reader = reader;

			MoveToBlob (blob);
#if DEBUG
			this.sig_length = ReadCompressedUInt32 ();
			this.start = (uint) position;
#else
			ReadCompressedUInt32 ();
#endif
		}

		void MoveToBlob (uint blob)
		{
			position = (int) (reader.image.BlobHeap.Offset + blob);
		}

		MetadataToken ReadTypeTokenSignature ()
		{
			return CodedIndex.TypeDefOrRef.GetMetadataToken (ReadCompressedUInt32 ());
		}

		GenericParameter GetGenericParameter (GenericParameterType type, uint var)
		{
			var context = reader.context;

			if (context == null)
				throw new NotSupportedException ();

			IGenericParameterProvider provider;

			switch (type) {
			case GenericParameterType.Type:
				provider = context.Type;
				break;
			case GenericParameterType.Method:
				provider = context.Method;
				break;
			default:
				throw new NotSupportedException ();
			}

			int index = (int) var;

			if (!context.IsDefinition)
				CheckGenericContext (provider, index);

			return provider.GenericParameters [index];
		}

		static void CheckGenericContext (IGenericParameterProvider owner, int index)
		{
			for (int i = owner.GenericParameters.Count; i <= index; i++)
				owner.GenericParameters.Add (new GenericParameter (owner));
		}

		public void ReadGenericInstanceSignature (IGenericInstance instance)
		{
			var arity = ReadCompressedUInt32 ();

			// TODO: adjust provider if not definition ?

			for (int i = 0; i < arity; i++)
				instance.GenericArguments.Add (ReadTypeSignature ());
		}

		ArrayType ReadArrayTypeSignature ()
		{
			var array = new ArrayType (ReadTypeSignature ());

			var rank = ReadCompressedUInt32 ();

			var sizes = new uint [ReadCompressedUInt32 ()];
			for (int i = 0; i < sizes.Length; i++)
				sizes [i] = ReadCompressedUInt32 ();

			var low_bounds = new int [ReadCompressedUInt32 ()];
			for (int i = 0; i < low_bounds.Length; i++)
				low_bounds [i] = ReadCompressedInt32 ();

			array.Dimensions.Clear ();

			for (int i = 0; i < rank; i++) {
				int? lower = null, upper = null;

				if (i < low_bounds.Length)
					lower = low_bounds [i];

				if (i < sizes.Length)
					upper = lower + (int) sizes [i] - 1;

				array.Dimensions.Add (new ArrayDimension (lower, upper));
			}

			return array;
		}

		TypeReference GetTypeDefOrRef (MetadataToken token)
		{
			return reader.GetTypeDefOrRef (token);
		}

		public TypeReference ReadTypeSignature ()
		{
			return ReadTypeSignature ((ElementType) ReadByte ());
		}

		TypeReference ReadTypeSignature (ElementType etype)
		{
			switch (etype) {
			case ElementType.ValueType: {
				var value_type = GetTypeDefOrRef (ReadTypeTokenSignature ());
				value_type.IsValueType = true;
				return value_type;
			}
			case ElementType.Class:
				return GetTypeDefOrRef (ReadTypeTokenSignature ());
			case ElementType.Ptr:
				return new PointerType (ReadTypeSignature ());
			case ElementType.FnPtr: {
				var fptr = new FunctionPointerType ();
				ReadMethodSignature (fptr);
				return fptr;
			}
			case ElementType.ByRef:
				return new ByReferenceType (ReadTypeSignature ());
			case ElementType.Pinned:
				return new PinnedType (ReadTypeSignature ());
			case ElementType.SzArray:
				return new ArrayType (ReadTypeSignature ());
			case ElementType.Array:
				return ReadArrayTypeSignature ();
			case ElementType.CModOpt:
				return new OptionalModifierType (
					GetTypeDefOrRef (ReadTypeTokenSignature ()), ReadTypeSignature ());
			case ElementType.CModReqD:
				return new RequiredModifierType (
					GetTypeDefOrRef (ReadTypeTokenSignature ()), ReadTypeSignature ());
			case ElementType.Var:
				return GetGenericParameter (GenericParameterType.Type, ReadCompressedUInt32 ());
			case ElementType.MVar:
				return GetGenericParameter (GenericParameterType.Method, ReadCompressedUInt32 ());
			case ElementType.GenericInst: {
				var is_value_type = ReadByte () == (byte) ElementType.ValueType;
				var element_type = GetTypeDefOrRef (ReadTypeTokenSignature ());
				var generic_instance = new GenericInstanceType (element_type);

				ReadGenericInstanceSignature (generic_instance);

				generic_instance.IsValueType = is_value_type;
				return generic_instance;
			}
			case ElementType.Object: return TypeSystem.Object;
			case ElementType.Void: return TypeSystem.Void;
			case ElementType.Boolean: return TypeSystem.Boolean;
			case ElementType.Char: return TypeSystem.Char;
			case ElementType.I: return TypeSystem.IntPtr;
			case ElementType.U: return TypeSystem.UIntPtr;
			case ElementType.I1: return TypeSystem.SByte;
			case ElementType.U1: return TypeSystem.Byte;
			case ElementType.I2: return TypeSystem.Int16;
			case ElementType.U2: return TypeSystem.UInt16;
			case ElementType.I4: return TypeSystem.Int32;
			case ElementType.U4: return TypeSystem.UInt32;
			case ElementType.I8: return TypeSystem.Int64;
			case ElementType.U8: return TypeSystem.UInt64;
			case ElementType.R4: return TypeSystem.Single;
			case ElementType.R8: return TypeSystem.Double;
			case ElementType.String: return TypeSystem.String;
			case ElementType.TypedByRef: return TypeSystem.TypedReference;
			}

			throw new NotSupportedException (etype.ToString ());
		}

		public void ReadMethodSignature (IMethodSignature method)
		{
			var calling_convention = ReadByte ();
			method.CallingConvention = (MethodCallingConvention) calling_convention;
			method.HasThis = (calling_convention & 0x20) != 0;
			method.ExplicitThis = (calling_convention & 0x40) != 0;

			var generic_context = method as MethodReference;
			if (generic_context != null)
				reader.context = generic_context;

			if ((calling_convention & 0x10) != 0)
				// TODO: adjust provider if not definition
				ReadCompressedUInt32 ();

			// TODO: more call_conv

			var param_count = ReadCompressedUInt32 ();

			method.MethodReturnType.ReturnType = ReadTypeSignature ();

			if (param_count == 0)
				return;

			var parameters = method.Parameters;

			for (int i = 0; i < param_count; i++)
				parameters.Add (new ParameterDefinition (ReadTypeSignature ()));
		}

		public object ReadConstantSignatureLittleEndian (ElementType type)
		{
			return ReadPrimitiveValue (type);
		}

		public object ReadConstantSignatureBigEndian (ElementType type, byte [] blob)
		{
			switch (type) {
			case ElementType.Boolean:
				return blob [0] == 1;
			case ElementType.I1:
				return (sbyte) blob [0];
			case ElementType.U1:
				return (byte) blob [0];
			}

			// BinaryReader always read it's data in LE format

			var reader = new BinaryReader (new MemoryStream (blob));
			switch (type) {
			case ElementType.Char:
				return (char) reader.ReadUInt16 ();
			case ElementType.I2:
				return reader.ReadInt16 ();
			case ElementType.I4:
				return reader.ReadInt32 ();
			case ElementType.I8:
				return reader.ReadInt64 ();
			case ElementType.U2:
				return reader.ReadUInt16 ();
			case ElementType.U4:
				return reader.ReadUInt32 ();
			case ElementType.U8:
				return reader.ReadUInt64 ();
			case ElementType.R4:
				return reader.ReadSingle ();
			case ElementType.R8:
				return reader.ReadDouble ();
			default:
				throw new ArgumentException ();
			}
		}

		public void ReadCustomAttributeConstructorArguments (CustomAttribute attribute, IList<ParameterDefinition> parameters)
		{
			if (parameters.Count == 0)
				return;

			attribute.arguments = new CustomAttributeArgumentCollection ();

			for (int i = 0; i < parameters.Count; i++) {
				var parameter = parameters [i];
				attribute.arguments.Add (
					ReadCustomAttributeFixedArgument (parameter.ParameterType));
			}
		}

		CustomAttributeArgument ReadCustomAttributeFixedArgument (TypeReference type)
		{
			if (type.IsArray)
				return ReadCustomAttributeFixedArrayArgument ((ArrayType) type);

			return ReadCustomAttributeElement (type);
		}

		public void ReadCustomAttributeNamedArguments (CustomAttribute attribute, ushort count)
		{
			for (int i = 0; i < count; i++)
				ReadCustomAttributeNamedArgument (attribute);
		}

		void ReadCustomAttributeNamedArgument (CustomAttribute attribute)
		{
			var kind = ReadByte ();
			var type = ReadCustomAttributeFieldOrPropType ();
			var name = ReadUTF8String ();

			var slots = GetCustomAttributeSlots (kind, attribute);

			slots [name] = ReadCustomAttributeFixedArgument (type);
		}

		IDictionary<string, CustomAttributeArgument> GetCustomAttributeSlots (byte kind, CustomAttribute attribute)
		{
			switch (kind) {
			case 0x53:
				return GetCustomAttributeSlots (ref attribute.fields);
			case 0x54:
				return GetCustomAttributeSlots (ref attribute.properties);
			default:
				throw new NotSupportedException ();
			}
		}

		Dictionary<string, CustomAttributeArgument> GetCustomAttributeSlots (ref Dictionary<string, CustomAttributeArgument> slots)
		{
			if (slots != null)
				return slots;

			return slots = new Dictionary<string, CustomAttributeArgument> ();
		}

		CustomAttributeArgument ReadCustomAttributeFixedArrayArgument (ArrayType type)
		{
			var length = ReadUInt32 ();

			if (length == 0xffffffff)
				return new CustomAttributeArgument (type, null);

			if (length == 0)
				return new CustomAttributeArgument (type, Empty<CustomAttributeArgument>.Array);

			var arguments = new CustomAttributeArgument [length];
			var element_type = ((ArrayType) type).ElementType;

			for (int i = 0; i < length; i++)
				arguments [i] = ReadCustomAttributeElement (element_type);

			return new CustomAttributeArgument (type, arguments);
		}

		CustomAttributeArgument ReadCustomAttributeElement (TypeReference type)
		{
			if (type.IsArray)
				return ReadCustomAttributeFixedArrayArgument ((ArrayType) type);

			if (type.etype == ElementType.Object)
				return ReadCustomAttributeElement (ReadCustomAttributeFieldOrPropType ());

			return new CustomAttributeArgument (type, ReadCustomAttributeElementValue (type));
		}

		object ReadCustomAttributeElementValue (TypeReference type)
		{
			var etype = type.etype;

			switch (etype) {
			case ElementType.String:
				return ReadUTF8String ();
			case ElementType.None:
				if (type.FullName == Constants.Type)
					return ReadCustomAttributeTypeReference ();
				else
					return ReadCustomAttributeEnum (type);
			default:
				return ReadPrimitiveValue (etype);
			}
		}

		object ReadPrimitiveValue (ElementType type)
		{
			switch (type) {
			case ElementType.Boolean:
				return ReadByte () == 1;
			case ElementType.I1:
				return (sbyte) ReadByte ();
			case ElementType.U1:
				return ReadByte ();
			case ElementType.Char:
				return (char) ReadUInt16 ();
			case ElementType.I2:
				return ReadInt16 ();
			case ElementType.U2:
				return ReadUInt16 ();
			case ElementType.I4:
				return ReadInt32 ();
			case ElementType.U4:
				return ReadUInt32 ();
			case ElementType.I8:
				return ReadInt64 ();
			case ElementType.U8:
				return ReadUInt64 ();
			case ElementType.R4:
				return ReadSingle ();
			case ElementType.R8:
				return ReadDouble ();
			default:
				throw new NotImplementedException (type.ToString ());
			}
		}

		TypeReference ReadCustomAttributeFieldOrPropType ()
		{
			var etype = (ElementType) ReadByte ();

			switch (etype) {
			case ElementType.Boxed:
				return TypeSystem.Object;
			case ElementType.SzArray:
				return new ArrayType (ReadCustomAttributeFieldOrPropType ());
			case ElementType.Enum:
				return ReadCustomAttributeEnumType ();
			case ElementType.Boolean:
				return TypeSystem.Boolean;
			case ElementType.Char:
				return TypeSystem.Char;
			case ElementType.I1:
				return TypeSystem.SByte;
			case ElementType.U1:
				return TypeSystem.Byte;
			case ElementType.I2:
				return TypeSystem.Int16;
			case ElementType.U2:
				return TypeSystem.UInt16;
			case ElementType.I4:
				return TypeSystem.Int32;
			case ElementType.U4:
				return TypeSystem.UInt32;
			case ElementType.I8:
				return TypeSystem.Int64;
			case ElementType.U8:
				return TypeSystem.UInt64;
			case ElementType.R4:
				return TypeSystem.Single;
			case ElementType.R8:
				return TypeSystem.Double;
			case ElementType.String:
				return TypeSystem.String;
			default:
				throw new NotImplementedException (etype.ToString ());
			}
		}

		TypeReference ReadCustomAttributeTypeReference ()
		{
			return ParseFullyQualifiedTypeName (ReadUTF8String ());
		}

		TypeReference ReadCustomAttributeEnumType ()
		{
			return ParseFullyQualifiedTypeName (ReadUTF8String ());
		}

		static string Normalize (string type)
		{
			return type.Replace ('+', '/');
		}

		TypeReference ParseFullyQualifiedTypeName (string fqname) // most horrible method so far
		{
			IMetadataScope scope;
			var module = reader.module;

			var separator = fqname.IndexOf (',');

			if (separator > 0) {
				var assembly_name = fqname.Substring (separator + 1);
				var reference = AssemblyNameReference.Parse (assembly_name);

				if (module.Assembly != null && module.Assembly.Name.FullName == reference.FullName)
					return module.Types [Normalize (fqname.Substring (0, separator))];

				MatchReference (ref reference);

				scope = reference;
			} else {
				var current_type = module.Types [Normalize (fqname)];
				if (current_type != null)
					return current_type;

				scope = module.TypeSystem.Corlib;
			}

			var type_name = separator > 0 ? fqname.Substring (0, separator) : fqname;

			var names = type_name.Split ('+');

			var outer_fullname = names [0];
			separator = outer_fullname.LastIndexOf ('.');

			string name, @namespace;
			if (separator > 0) {
				@namespace = outer_fullname.Substring (0, separator);
				name = outer_fullname.Substring (separator + 1);
			} else {
				@namespace = string.Empty;
				name = outer_fullname;
			}

			var type = new TypeReference (name, @namespace, scope);
			type.Module = module;

			for (int i = 1; i < names.Length; i++) {
				var nested_type = new TypeReference (names [i], string.Empty, scope);
				nested_type.Module = module;
				nested_type.DeclaringType = type;
				type = nested_type;
			}

			return type;
		}

		void MatchReference (ref AssemblyNameReference candidate)
		{
			var references = reader.module.AssemblyReferences;
			for (int i = 0; i < references.Count; i++) {
				var reference = references [i];
				if (reference.FullName != candidate.FullName)
					continue;

				candidate = reference;
				return;
			}
		}

		object ReadCustomAttributeEnum (TypeReference enum_type)
		{
			var type = enum_type.Resolve ();
			if (!type.IsEnum)
				throw new ArgumentException ();

			return ReadCustomAttributeElementValue (type.GetEnumUnderlyingType ());
		}

		string ReadUTF8String ()
		{
			if (buffer [position] == 0xff) {
				position++;
				return null;
			}

			var length = (int) ReadCompressedUInt32 ();
			var @string = Encoding.UTF8.GetString (this.buffer, position, length);
			position += length;
			return @string;
		}

		public void Dispose ()
		{
#if DEBUG
			if (position - start > sig_length)
				throw new InvalidOperationException ();
#endif
		}
	}
}
